package com.clx.admin.service.Impl;

import cn.dev33.satoken.stp.StpUtil;
import com.alibaba.fastjson2.JSON;
import com.clx.common.domain.entity.DiskUser;
import org.springframework.core.io.Resource;
import org.springframework.core.io.InputStreamResource;
import com.clx.admin.mapper.FileMapper;
import com.clx.admin.service.FileService;
import com.github.pagehelper.PageHelper;
import com.clx.common.domain.dto.FileUploadChunkDto;
import com.clx.common.domain.entity.DiskFile;
import com.clx.common.domain.vo.AjaxResult;
import com.clx.common.enums.FileType;
import io.minio.*;
import io.minio.errors.MinioException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.InputStreamResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.clx.common.utils.DateUtils;
import com.clx.common.utils.MinioUtils;
import com.clx.common.utils.RedisCache;
import com.clx.common.utils.StringUtils;

import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * 文件表(File)表服务实现类
 *
 * @author clx
 * @since 2025-01-07 18:51:36
 */
@Service("fileService")
@RequiredArgsConstructor
@Slf4j
public class FileServiceImpl implements FileService {

    private final MinioClient minioClient;

    private final FileMapper fileMapper;

    private final RedisCache redisCache;

    private final MinioUtils minioUtils;

    @Value("${minio.bucketName}")
    private String bucketName;

    @Value("${File.profile}")
    private  String filePath;


    //存储索引值
    private Set<Long> indexSet = new HashSet<>(Arrays.asList(2L, 3L, 4L,5L,6L,7L,8L,9L));

    /**
     * 查询数据
     *
     * @param id 主键
     * @return 实例对象
     */
    @Override
    public DiskFile selectFileById(Long id) {
        return fileMapper.selectFileById(id);
    }

    /**
     * 查询列表
     *
     * @param file 筛选条件
     * @return 查询结果
     */
    @Override
    public List<DiskFile> selectFileList(DiskFile file) {
        DiskFile entity = file;
        return (fileMapper.selectFileList(entity));
    }

    /**
     * 新增数据
     *
     * @param file 实例对象
     * @return 受影响的行数，新增成功通常返回1，失败返回0等
     */
    @Override
    public int insertFile(DiskFile file) {
        DiskFile entity = file;
        entity.setCreateTime(DateUtils.getNowDate());
        return (fileMapper.insertFile(entity));
    }

    /**
     * 修改数据
     *
     * @param file 实例对象
     * @return 受影响的行数，一般成功修改返回1
     */
    @Override
    public int updateFile(DiskFile file) {
        DiskFile entity = file;
        entity.setUpdateTime(DateUtils.getNowDate());
        return (fileMapper.updateFile(entity));
    }

    /**
     * 批量删除数据
     *
     * @param ids 需要删除的主键数组
     * @return 受影响的行数，即成功删除的记录数
     */
    @Override
    public int deleteFileByIds(Long[] ids) {
        return (fileMapper.deleteFileByIds(ids));
    }

    /**
     * 通过主键删除数据
     *
     * @param id 主键
     * @return 是否成功（以受影响行数判断，成功返回1等）
     */
    @Override
    public int deleteFileById(Long id) {
        return (fileMapper.deleteFileById(id));
    }


    /**
     * 分页查询（无参数）
     * @param pageNum 当前页码（从1开始）
     * @param pageSize 每页大小
     * @param index 菜单索引
     * @return 分页结果
     */
    @Override
    public List<DiskFile> paging(int pageNum, int pageSize,int index) {
        long loginId = StpUtil.getLoginIdAsLong();
        //开启分页
        PageHelper.startPage(pageNum,pageSize);
        DiskFile file = DiskFile.builder()
                .userId(loginId)
                .status(1)
                .build();
        if (index== FileType.MYFILE.getCode()||index==FileType.ALLFILES.getCode()){
            List<DiskFile> fileList = fileMapper.selectAllFiles(file);
            return fileList;
        }else if (index==FileType.RECYCLE.getCode()){
            DiskFile tempfile = DiskFile.builder()
                    .userId(loginId)
                    .status(2)
                    .build();
            List<DiskFile> fileList = fileMapper.selectAllFiles(tempfile);
            return fileList;
        }
        file.setFileType(Long.valueOf(index));
        List<DiskFile> fileList = fileMapper.selectAllFiles(file);
        return fileList;
    }


    /**
     * 文件小于20mb直接上传
     */
    @Override
    public AjaxResult uploadSmall(FileUploadChunkDto fileUploadChunkDto) {
        String redisKey=filePath+fileUploadChunkDto.getFileMD5()+fileUploadChunkDto.getRelativePath();
        Long userId = StpUtil.getLoginIdAsLong();
        //1. 2.检查redis是否存在唯一标识md5，没有创建记录，有返回
        switch (check(redisKey,fileUploadChunkDto,userId)){
            case 1:
                return AjaxResult.success("秒传成功",1);
            case 2:
                return AjaxResult.success("文件已经放入回收站",2);
            case 3:
                return AjaxResult.success("文件已经上传");
            case 0:
                return AjaxResult.error("服务器异常");
            default:
                break;
        }
        //3.判断是否存在bucket，不存在创建
        if (!minioUtils.bucketExists(bucketName)){
            minioUtils.createBucket(bucketName);
        }

        //4.存入rediskey
        String key="0";
        redisCache.setCacheMapValue(redisKey,key,fileUploadChunkDto.getRelativePath());
        //5.存入minio
        try {
            minioUtils.putObject(
                    fileUploadChunkDto.getFileName(),
                    fileUploadChunkDto.getChunk().getInputStream(),
                    fileUploadChunkDto.getChunk().getContentType(),
                    bucketName,
                    fileUploadChunkDto.getChunk()
            );
            //存入完成更新
            DiskFile finalFile = DiskFile.builder()
                    .fileMd5(fileUploadChunkDto.getFileMD5())
                    .status(1)
                    .fileName(fileUploadChunkDto.getFileName())
                    .filePath(fileUploadChunkDto.getRelativePath())
                    .build();
            fileMapper.updateOneFile(finalFile);
            //同时删除redis查询记录
//            for (Long index : indexSet) {
//                String tkey="file_type:"+StpUtil.getLoginIdAsLong()+"_"+index;
//                if (redisCache.hasKey(tkey)){
//                    redisCache.deleteObject(tkey);
//                }
//            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

        return AjaxResult.success("文件小于20mb上传完成");
    }


    /**
     * 下载文件
     * @param list
     * @return
     */
    @Override
    public void downLoad(List<DiskFile> list, HttpServletResponse response) {
        response.setContentType("application/zip");
        response.setCharacterEncoding("utf-8");
        String zipFileName = "download.zip";
        try {
            response.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(zipFileName, String.valueOf(StandardCharsets.UTF_8)));
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }

        try (ZipOutputStream zipOutputStream = new ZipOutputStream(response.getOutputStream())) {
            for (DiskFile diskFile : list) {
                GetObjectArgs getObjectArgs = GetObjectArgs.builder()
                        .bucket(bucketName)
                        .object(diskFile.getFileName())
                        .build();
                try (InputStream inputStream = minioClient.getObject(getObjectArgs)) {
                    ZipEntry zipEntry = new ZipEntry(diskFile.getFileName());
                    zipOutputStream.putNextEntry(zipEntry);
                    byte[] buffer = new byte[4096];
                    int bytesRead;
                    while ((bytesRead = inputStream.read(buffer)) != -1) {
                        zipOutputStream.write(buffer, 0, bytesRead);
                    }
                    zipOutputStream.closeEntry();
                }
            }
        } catch (Exception e) {
            throw new RuntimeException();
        }
    }


    /**
     * 新建文件夹
     * @param folderName
     * @return
     */
    @Override
    public AjaxResult newFolder(String folderName,Long parentId) {
        long userId = StpUtil.getLoginIdAsLong();
        DiskFile file = DiskFile.builder()
                .fileName(folderName)
                .status(1)
                .userId(userId)
                .fileType(3L)
                .filePath(folderName)
                .build();
        //查询是否存在
        DiskFile select = fileMapper.selectByFileOne(file);
        if (ObjectUtils.isNotEmpty(select)){
            //证明存在
            return AjaxResult.error("已经有同名文件夹");
        }
        //判断
        if (ObjectUtils.isNotEmpty(parentId)){
            //证明在二级目录创建文件夹
            file.setParentId(parentId);
            int i = fileMapper.insertFile(file);
            if (i<0){
                return AjaxResult.error("创建文件夹失败");
            }
            return AjaxResult.success("文件夹创建成功",file);
        }
        int i = fileMapper.insertFile(file);
        if (i<0){
            return AjaxResult.error("创建文件夹失败");
        }
        return AjaxResult.success("文件夹创建成功",file);
    }






    /**
     * 上传分块文件
     *
     */
    @Override
    public AjaxResult upload(FileUploadChunkDto fileUploadChunkDto) {
        //如果传来的是文件，relativePath的值是fileName
        String redisKey=filePath+fileUploadChunkDto.getFileMD5()+fileUploadChunkDto.getRelativePath();
        Long userId = StpUtil.getLoginIdAsLong();

        //1. 2.检查redis是否存在唯一标识md5，没有创建记录，有返回
        switch (check(redisKey,fileUploadChunkDto,userId)){
            case 1:
                return AjaxResult.success("秒传成功",1);
            case 2:
                return AjaxResult.success("文件已经放入回收站",2);
            case 3:
                return AjaxResult.success("文件已经上传");
            case 0:
                return AjaxResult.error("服务器异常");
            default:
                break;
        }
        //3.判断是否存在bucket，不存在创建
        if (!minioUtils.bucketExists(bucketName)){
            minioUtils.createBucket(bucketName);
        }

        //4.将每个分块的名字为MD5标识+_+索引名+.part后缀名存储到minio中,同时把整个文件的MD5唯一标识和文件分片索引存储到redis
        String tempFileName=fileUploadChunkDto.getFileMD5()+"_"+fileUploadChunkDto.getChunkIndex()+".part";
        try {
            minioUtils.putObject(
                    tempFileName,
                    fileUploadChunkDto.getChunk().getInputStream(),
                    fileUploadChunkDto.getChunk().getContentType(),
                    bucketName,
                    fileUploadChunkDto.getChunk()
            );
            //存储到redis中
            String key= String.valueOf(fileUploadChunkDto.getChunkIndex());
            redisCache.setCacheMapValue(redisKey,key,fileUploadChunkDto.getRelativePath());
            // TODO 可以设置一个过期时间，例如1小时后如果还未完成整个文件的合并等操作，相关分块信息过期 (目前应该不用，因为我使用redis来判断是否上传)
            //redisCache.expire(fileMD5, 1, TimeUnit.HOURS);
        } catch (Exception e) {
            //记录日志
            log.info("存储到minio失败",e);
        }
        //更新上传的分块数量
        DiskFile finalFile = DiskFile.builder()
                .fileMd5(fileUploadChunkDto.getFileMD5())
                .uploadedChunks(fileUploadChunkDto.getChunkIndex()+1)//每一次请求接口，就把索引+1给到上传数量
                .build();
        int i = fileMapper.updateFileBychunks(finalFile);
        if (i<0){
            return AjaxResult.error("服务器异常");
        }
        //5.当chunkIndex=totalChunks-1的时候分块上传完成,更新数据库中的状态
        if (fileUploadChunkDto.getChunkIndex()!=fileUploadChunkDto.getTotalChunks()-1){
            return AjaxResult.success(null);

        }
        return AjaxResult.success(null);
    }


    /**
     * 合并分片
     */
    @Override
    public AjaxResult merge(FileUploadChunkDto fileUploadChunkDto) {
        String fileMD5 = fileUploadChunkDto.getFileMD5();
        String fileName = fileUploadChunkDto.getFileName();
        Long totalChunks = fileUploadChunkDto.getTotalChunks();
        String relativePath = fileUploadChunkDto.getRelativePath();

        // 1. 从Redis中获取分块信息 并从数据查看状态
        String redisKey = filePath + fileMD5+relativePath;
        if (!redisCache.hasKey(redisKey)){
            return AjaxResult.error("该文件还没有进行分片");
        }
        Map<String, String> chunkMap = redisCache.getCacheMap(redisKey);

        if (chunkMap == null || chunkMap.isEmpty()) {
            return AjaxResult.error("分块信息不存在或已过期,请重新上传");
        }

        // 2. 从MinIO中获取所有分块
        List<ComposeSource> sources = new ArrayList<>();
        for (long i = 0; i < totalChunks; i++) {
            String chunkName = fileMD5 + "_" + i + ".part";
            sources.add(ComposeSource.builder()
                    .bucket(bucketName)
                    .object(chunkName)
                    .build());
        }

        try {
            // 合并分块
            minioClient.composeObject(ComposeObjectArgs.builder()
                    .bucket(bucketName)
                    .object(fileName)
                    .sources(sources)
                    .build());

            // 3. 合并完成后，重新生成文件的MD5标识
            String mergedFileMD5 = calculateMD5( bucketName, fileName);

            // 4. 与原来的MD5比对
            if (StringUtils.equals(mergedFileMD5,fileMD5)) {
                // 上传成功，更新数据库状态
                DiskFile finalFile = DiskFile.builder()
                        .fileMd5(fileMD5)
                        .status(1)
                        .fileSize(fileUploadChunkDto.getFileSize())
                        .filePath(relativePath)
                        .build();
                fileMapper.updateFileStatus(finalFile);

                //5. 删除MinIO中的分块文件
                for (long i = 0; i < totalChunks; i++) {
                    String chunkName = fileMD5 + "_" + i + ".part";
                    minioClient.removeObject(RemoveObjectArgs.builder()
                            .bucket(bucketName)
                            .object(chunkName)
                            .build());
                }
                //同时删除redis查询记录
                for (Long index : indexSet) {
                    String key="file_type:"+StpUtil.getLoginIdAsLong()+"_"+index;
                    if (redisCache.hasKey(key)){
                        redisCache.deleteObject(key);
                    }
                }
                return AjaxResult.success("文件上传成功");
            } else {
                // MD5不一致，上传失败
                //删除Redis的缓存、分块文件、合并文件
                redisCache.deleteObject(redisKey);
                //删除分块文件
                for (long i = 0; i < totalChunks; i++) {
                    String chunkName = fileMD5 + "_" + i + ".part";
                    minioClient.removeObject(RemoveObjectArgs.builder()
                            .bucket(bucketName)
                            .object(chunkName)
                            .build());
                }
                //删除合并文件
                minioClient.removeObject(RemoveObjectArgs.builder()
                                .bucket(bucketName)
                                .object(fileName)
                                .build());
                return AjaxResult.error("文件合并失败,MD5校验不通过,请重新上传");
            }
        } catch (Exception e) {
            log.error("文件合并失败", e);
            return AjaxResult.error("文件合并失败");
        }
    }


    /**
     * 根据用户id查询文件
     */
    @Override
    public AjaxResult selectFile() {
        Long loginId =StpUtil.getLoginIdAsLong();
        DiskFile file = DiskFile.builder()
                .userId(loginId)
                .status(1)
                .build();
        List<DiskFile> fileList = fileMapper.selectFileListByFolader(file);


        return AjaxResult.success(fileList);
    }



    /**
     * 返回文件类型对应的list
     */
    @Override
    public AjaxResult selectByType(Long index) {
        if (ObjectUtils.isEmpty(index)||index<0 ){
            return AjaxResult.error("索引返回错误");
        }
        Long loginId =StpUtil.getLoginIdAsLong();
        String key="file_type:"+loginId+"_"+index;
        //先查是否有缓存
        if (redisCache.hasKey(key)){
            String cacheObject = redisCache.getCacheObject(key);
            List<DiskFile> cacheList = JSON.parseArray(cacheObject, DiskFile.class);
            return AjaxResult.success("文件类型返回成功",cacheList);
        }
        DiskFile file = DiskFile.builder()
                .userId(loginId)
                .status(1)
                .build();
        if (index==FileType.MYFILE.getCode()||index==FileType.ALLFILES.getCode()){
            List<DiskFile> fileList = fileMapper.selectFileListByFolader(file);
            //存入缓存  此时要把fileList转成json格式，要不然不会缓存值为null的字段
            String jsonString = JSON.toJSONString(fileList);
            if (StringUtils.isNotEmpty(jsonString)){
                redisCache.setCacheObject(key,jsonString);
            }
            return AjaxResult.success("文件类型返回成功",fileList);
        }else if (index==FileType.RECYCLE.getCode()){
            DiskFile tempfile = DiskFile.builder()
                    .userId(loginId)
                    .status(2)
                    .build();
            List<DiskFile> fileList = fileMapper.selectFileListByFolader(tempfile);
            //存入缓存
            String jsonString = JSON.toJSONString(fileList);
            if (StringUtils.isNotEmpty(jsonString)){
                redisCache.setCacheObject(key,jsonString);
            }
            return AjaxResult.success("文件类型返回成功",fileList);
        }
        file.setFileType(index);
        List<DiskFile> fileList = fileMapper.selectFileListByFolader(file);
        //存入缓存
        String jsonString = JSON.toJSONString(fileList);
        if (StringUtils.isNotEmpty(jsonString)){
            redisCache.setCacheObject(key,jsonString);
        }
        return AjaxResult.success("文件类型返回成功",fileList);
    }


    /**
     * 搜索功能
     * @param value
     * @return
     */
    @Override
    public List<DiskFile> searchByFile(String value,int pageNum, int pageSize) {
        PageHelper.startPage(pageNum,pageSize);
        long userId = StpUtil.getLoginIdAsLong();
        //进行模糊匹配
        List<DiskFile> fileList = fileMapper.searchByFile(value,userId);
        return fileList;
    }

    /**
     * 合并文件夹分块
     */
    @Override
    public AjaxResult mergeChunks(FileUploadChunkDto fileUploadChunkDto) {
        String fileMD5 = fileUploadChunkDto.getFileMD5();
        Long totalChunks = fileUploadChunkDto.getTotalChunks();
        String relativePath = fileUploadChunkDto.getRelativePath();
        String fileName = fileUploadChunkDto.getFileName();
        Long parentId = fileUploadChunkDto.getParentId();
        String redisKey = filePath + fileMD5+relativePath;


        if (!redisCache.hasKey(redisKey)){
            return AjaxResult.error("该文件还没有进行分片");
        }
        Map<String, String> chunkMap = redisCache.getCacheMap(redisKey);

        if (chunkMap == null || chunkMap.isEmpty()) {
            return AjaxResult.error("分块信息不存在或已过期");
        }
        // 构建合并源
        List<ComposeSource> sources = new ArrayList<>();
        for (long i = 0; i < totalChunks; i++) {
            String chunkName = fileMD5 + "_" + i + ".part";
            sources.add(ComposeSource.builder()
                    .bucket(bucketName)
                    .object(chunkName)
                    .build());
        }

        try {
            // 合并到指定路径
            minioClient.composeObject(ComposeObjectArgs.builder()
                    .bucket(bucketName)
                    .object(relativePath) // 使用相对路径作为对象名
                    .sources(sources)
                    .build());

            // 合并完成后，重新生成文件的MD5标识
            String mergedFileMD5 = calculateMD5( bucketName, relativePath);
            // 与原来的MD5比对
            if (StringUtils.equals(mergedFileMD5,fileMD5)) {
                // 上传成功，更新数据库状态
                DiskFile finalFile = DiskFile.builder()
                        .fileMd5(fileMD5)
                        .fileName(fileName)
                        .status(1)
                        .fileSize(fileUploadChunkDto.getFileSize())
                        .filePath(relativePath)
                        .parentId(parentId)
                        .build();
                fileMapper.updateFileStatusAndParentId(finalFile);

                //5. 删除MinIO中的分块文件
                for (long i = 0; i < totalChunks; i++) {
                    String chunkName = fileMD5 + "_" + i + ".part";
                    minioClient.removeObject(RemoveObjectArgs.builder()
                            .bucket(bucketName)
                            .object(chunkName)
                            .build());
                }
                //同时删除redis查询记录
                for (Long index : indexSet) {
                    String key="file_type:"+StpUtil.getLoginIdAsLong()+"_"+index;
                    if (redisCache.hasKey(key)){
                        redisCache.deleteObject(key);
                    }
                }
                return AjaxResult.success(null);
            } else {
                // MD5不一致，上传失败
                //删除Redis的缓存、分块文件、合并文件
                redisCache.deleteObject(redisKey);
                //删除分块文件
                for (long i = 0; i < totalChunks; i++) {
                    String chunkName = fileMD5 + "_" + i + ".part";
                    minioClient.removeObject(RemoveObjectArgs.builder()
                            .bucket(bucketName)
                            .object(chunkName)
                            .build());
                }
                //删除合并文件
                minioClient.removeObject(RemoveObjectArgs.builder()
                        .bucket(bucketName)
                        .object(fileName)
                        .build());
                //删除文件夹上传记录
                fileMapper.deleteFileById(parentId);
                return AjaxResult.error("文件合并失败,MD5校验不通过,请重新上传");
            }
        } catch (Exception e) {
            log.error("文件夹合并失败", e);
            return AjaxResult.error("合并失败: " + e.getMessage());
        }

    }



    /**
     * 上传文件夹之前获取文件夹名称
     * 根据文件的相对路径，把文件夹创建
     * @param
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public AjaxResult getFolderName(Map<String, String> params) {
        String path = params.get("path");
        Long userId = StpUtil.getLoginIdAsLong();

        String[] parts = path.split("/");
        Long parentId = null;
        StringBuilder currentPath = new StringBuilder();

        for (String folderName : parts) {
            // 构建当前完整路径
            if (currentPath.length() > 0) {
                currentPath.append("/");
            }
            currentPath.append(folderName);

            DiskFile query = DiskFile.builder()
                    .fileName(folderName)
                    .parentId(parentId)
                    .userId(userId)
                    .fileType(3L)
                    .build();

            DiskFile existing = fileMapper.selectByFileOne(query);
            if (existing != null) {
                parentId = existing.getId();
                // 保持路径继续拼接
                continue;
            }

            DiskFile newFolder = DiskFile.builder()
                    .fileName(folderName)
                    .parentId(parentId)
                    .userId(userId)
                    .fileType(3L)
                    .filePath(currentPath.toString()) // 关键修改点
                    .status(1)
                    .createTime(DateUtils.getNowDate())
                    .build();

            fileMapper.insertFile(newFolder);
            parentId =newFolder.getId();
        }

        return AjaxResult.success(null, parentId);
    }



    /**
     * 通过文件夹名称获取文件内容
     * @param folderName
     * @param filePath
     * @return
     */
    @Override
    public List<DiskFile> listObjects(String filePath, String folderName,int pageNum,int pageSize) {
        long userId = StpUtil.getLoginIdAsLong();
        DiskFile file = DiskFile.builder()
                .filePath(filePath)
                .userId(userId)
                .fileName(folderName)
                .fileType(3L)
                .build();
        DiskFile tempFile = fileMapper.selectByFileOne(file);
        if (ObjectUtils.isEmpty(tempFile)){
            log.info("文件夹查询失败");
            return null;
        }
        PageHelper.startPage(pageNum,pageSize);
        DiskFile build = DiskFile.builder()
                .userId(userId)
                .parentId(tempFile.getId())
                .build();
        List<DiskFile> fileList = fileMapper.selectFileList(build);
        return fileList;
    }



    /**
     * 根据id返回上一级
     * @param id
     * @return
     */
    @Override
    public AjaxResult returnToParent(Long id) {

        long userId = StpUtil.getLoginIdAsLong();
        DiskFile file = fileMapper.selectFileById(id);
        if (ObjectUtils.isEmpty(file)){
            return AjaxResult.error("文件id不存在");
        }
        DiskFile build = DiskFile.builder()
                .parentId(file.getParentId())
                .userId(userId)
                .build();
        List<DiskFile> fileList = fileMapper.selectFileList(build);
        return AjaxResult.success("返回上一级查询文件成功",fileList);
    }



    /**
     * 彻底删除文件及文件夹
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public AjaxResult move(List<DiskFile> fileList) {
        if (ObjectUtils.isEmpty(fileList)) {
            return AjaxResult.error("要删除的文件列表为空");
        }
        long userId = StpUtil.getLoginIdAsLong();

        List<Long> allIds = new ArrayList<>(); // 所有待删除的ID（文件和文件夹）
        List<String> redisKeysList = new ArrayList<>();
        for (DiskFile file : fileList) {
            if (file.getFileType() != FileType.FOLDER.getCode()) {
                // 处理文件：记录ID和Redis Key，删除MinIO文件
                allIds.add(file.getId());
//                redisKeysList.add(filePath + file.getFileMd5() + file.getFilePath());
//                try {
//                    minioUtils.deleteFile(bucketName, file.getFileName());
//                } catch (Exception e) {
//                    throw new RuntimeException("文件删除失败: " + file.getFileName(), e);
//                }
            } else {
                allIds.add(file.getId());
                // 处理文件夹：递归收集所有子项ID，并记录文件夹路径
                collectAllChildrenIds(file.getId(), allIds,userId,redisKeysList,2);
//                try {
//                    // 删除MinIO中的文件夹内容
//                    minioUtils.deleteFolder(bucketName, file.getFileName());
//                } catch (Exception e) {
//                    throw new RuntimeException("文件夹删除失败: " + file.getFileName(), e);
//                }
            }
        }


        // 批量删除数据库记录
        if (!allIds.isEmpty()) {
            int deletedCount = fileMapper.deleteFileByIds(allIds.toArray(new Long[0]));
            if (deletedCount < 0) {
                throw new RuntimeException("数据库记录删除失败");
            }
        }

//        // 删除Redis中的MD5标识缓存
//        if (redisKeysList.isEmpty() ){
//            return AjaxResult.success("删除成功");
//        }
//        //同时删除查询记录
//        for (Long index : indexSet) {
//            String key="file_type:"+userId+"_"+index;
//            if (redisCache.hasKey(key)){
//                redisCache.deleteObject(key);
//            }
//        }
//
//        if ( !redisCache.deleteObject(redisKeysList)) {
//            throw new RuntimeException("Redis缓存删除失败");
//        }

        return AjaxResult.success("彻底删除文件成功");
    }


    /**
     * 删除文件以及文件夹到回收站(修改状态)
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public AjaxResult update(List<DiskFile> fileList) {
        if (ObjectUtils.isEmpty(fileList)) {
            return AjaxResult.error("要删除的文件列表为空");
        }
        long userId = StpUtil.getLoginIdAsLong();
        List<Long> fileIds = new ArrayList<>(); // 所有待修改状态的文件ID(无父级目录)
        List<Long> floderIds = new ArrayList<>(); // 所有待修改状态的文件夹以及子文件夹ID(有父级目录)
        List<String> redisKeysList = new ArrayList<>();
        for (DiskFile file : fileList) {
            if (file.getFileType() != FileType.FOLDER.getCode()) {
                // 处理文件：记录ID和Redis Key，删除MinIO文件
                if (ObjectUtils.isNotEmpty(file.getParentId())){
                    //如果文件的parentID不是空  把对应的文件ID以及父级ID存储到redis中
                    redisCache.setCacheMapValue("recycle:",file.getId().toString(),file.getParentId());
                }
                fileIds.add(file.getId());
            } else {
                floderIds.add(file.getId());
                // 处理文件夹：递归收集所有子项ID，并记录文件夹路径
                collectAllChildrenIds(file.getId(), floderIds,userId,redisKeysList,1);
            }
        }


        // 批量删除数据库记录
        if (!fileIds.isEmpty()) {
            int updateCount = fileMapper.updateFileByRecycle(fileIds.toArray(new Long[0]));
            if (updateCount < 0) {
                throw new RuntimeException("数据库记录修改失败");
            }
        }
        if (!floderIds.isEmpty()) {
            int updateCount = fileMapper.updateFloderByRecycle(floderIds.toArray(new Long[0]));
            if (updateCount < 0) {
                throw new RuntimeException("数据库记录修改失败");
            }
        }

        //同时删除redis查询记录
        for (Long index : indexSet) {
            String key="file_type:"+userId+"_"+index;
            if (redisCache.hasKey(key)){
                redisCache.deleteObject(key);
            }
        }

        return AjaxResult.success("成功将文件放到回收站,文件将会保存30天");
    }



    /**
     * 恢复回收站文件
     * @param
     * @return
     */
    @Override
    public AjaxResult recover(List<DiskFile> fileList) {
        for (DiskFile file : fileList) {
            file.setStatus(1);
            //1.从redis中寻找该文件是否有父级目录
            String redisKey="recycle:";
            Long parentId = redisCache.getCacheMapValue(redisKey, file.getId().toString());
            if (ObjectUtils.isNotEmpty(parentId)){
                //有父级目录信息,还原文件
                file.setParentId(parentId);
                int i = fileMapper.updateFile(file);
                if (i<0){
                    return AjaxResult.error("还原失败,请联系管理员");
                }
                //还原成功,删除redis缓存
                redisCache.deleteCacheMapValue(redisKey,file.getId().toString());
            }
            //2.没有父级目录,把状态更新为1
            int i = fileMapper.updateFile(file);
            if (i<0){
                return AjaxResult.error("还原失败,请联系管理员");
            }
        }
        return AjaxResult.success("还原成功");
    }



    /**
     * 重命名
     * @param
     * @return
     */
    @Override
    public AjaxResult rename(DiskFile file) {
        int i = fileMapper.updateFile(file);
        if (i<0){
            return AjaxResult.error("重命名失败,请联系管理员");
        }
        return AjaxResult.success("重命名成功");
    }

    /**
     * 递归收集所有子文件和文件夹的ID
     */
    private void collectAllChildrenIds(Long parentId, List<Long> idCollector,Long userId, List<String> redisKeysList,Integer status) {
        DiskFile build = DiskFile.builder()
                .userId(userId)
                .parentId(parentId)
                .status(status)
                .build();
        List<DiskFile> fileList = fileMapper.selectFileList(build);
        for (DiskFile file : fileList) {
            idCollector.add(file.getId());
            redisKeysList.add(filePath + file.getFileMd5() + file.getFilePath());
            DiskFile child = fileMapper.selectFileById(file.getId());
            if (child.getFileType() == FileType.FOLDER.getCode()) {
                collectAllChildrenIds(file.getId(), idCollector,userId,redisKeysList,status); // 递归处理子文件夹
            }
        }
    }

    /**
     * 重新生成MD5标识
     */
    public  String calculateMD5(String bucketName, String objectName) throws IOException, NoSuchAlgorithmException {
        MessageDigest digest = MessageDigest.getInstance("MD5");
        try (InputStream inputStream = minioClient.getObject(GetObjectArgs.builder()
                .bucket(bucketName)
                .object(objectName)
                .build())) {
            byte[] buffer = new byte[8192];
            int bytesRead;
            while ((bytesRead = inputStream.read(buffer))!= -1) {
                digest.update(buffer, 0, bytesRead);
            }


            byte[] hashBytes = digest.digest();
            StringBuilder sb = new StringBuilder();
            for (byte b : hashBytes) {
                sb.append(String.format("%02x", b));
            }
            return sb.toString();
        } catch (MinioException e) {
            log.error("Error occurred while fetching object from MinIO: {}", e.getMessage());
            throw new IOException("Failed to fetch object from MinIO", e);
        } catch (NoSuchAlgorithmException e) {
            log.error("MD5 algorithm not found: {}", e.getMessage());
            throw e;
        } catch (IOException e) {
            log.error("Error occurred while reading object stream: {}", e.getMessage());
            throw e;
        } catch (InvalidKeyException e) {
            throw new RuntimeException(e);
        }
    }


    /**
     * 根据文件扩展名判断文件类型
     * @param fileName 文件名（包含扩展名）
     * @return 文件类型ID
     */
    private Long determineFileType(String fileName) {
        String fileExtension = FilenameUtils.getExtension(fileName).toLowerCase();
        switch (fileExtension) {
            // 图片类型（补充WebP/SVG/RAW等格式）
            case "jpg":
            case "jpeg":
            case "png":
            case "gif":
            case "bmp":
            case "webp":
            case "svg":
            case "tiff":
            case "psd":
            case "heic":
            case "ico":
                return 4L;

            // 视频类型（补充常见编码格式）
            case "mp4":
            case "avi":
            case "mkv":
            case "flv":
            case "mov":
            case "wmv":
            case "mpeg":
            case "mpg":
            case "3gp":
            case "m4v":
            case "m2ts":
                return 5L;

            // 文档类型（补充办公文档/电子书/代码文件）
            case "doc":
            case "docx":
            case "pdf":
            case "txt":
            case "ppt":
            case "pptx":
            case "xls":
            case "xlsx":
            case "csv":
            case "md":
            case "rtf":
            case "epub":
            case "mobi":
            case "odt":
            case "odp":
            case "ods":
            case "java":
            case "py":
            case "js":
            case "html":
            case "css":
            case "cpp":
            case "h":
                return 6L;

            // 音频类型（补充无损格式）
            case "mp3":
            case "wav":
            case "aac":
            case "flac":
            case "ogg":
            case "wma":
            case "aiff":
            case "m4a":
                return 7L;

            // 压缩文件类型（补充Linux压缩格式）
            case "zip":
            case "rar":
            case "7z":
            case "tar":
            case "gz":
            case "bz2":
            case "xz":
            case "tgz":    // tar.gz
            case "tbz":    // tar.bz2
                return 8L;

            // 特殊类型文件（需要明确归类到其他）
            case "exe":
            case "bat":
            case "sh":
            case "jar":
            case "iso":
            case "dmg":
            case "apk":
            case "xml":
            case "json":
            case "yml":
            case "ini":
            case "cfg":
            case "sql":
            case "log":
                return 8L;

            // 默认处理逻辑
            default:
                // 空扩展名可能是文件夹，但建议单独验证
                return fileExtension.isEmpty() ? 3L : 8L;
        }
    }



    /**
     * 检查传来的文件是否存在
     * 检查文件是否已经被放到回收站
     * 秒传功能
     * @param redisKey 缓存文件的md5标识到redis的key
     * @param fileUploadChunkDto 接受前端传来的参数类
     * @param userId 用户Id
     * @return   1  秒传功能
     *           2  文件已经被放入回收站,不需要上传
     *           3  文件已经上传过了
     *           0  服务器异常
     *     任意数字  步过,执行之后的代码
     */
    public int check(String redisKey,FileUploadChunkDto fileUploadChunkDto,Long userId) {
        //1.根据MD5标识查看Redis是否有相同文件，如果有且状态以及上传完成，返回信息
        if (redisCache.hasKey(redisKey)) {
            //1.1查询数据库的用户是否上传这个文件
            DiskFile tempFile = DiskFile.builder()
                    .fileMd5(fileUploadChunkDto.getFileMD5())
                    .userId(userId)
                    .build();
            DiskFile file = fileMapper.selectByFileOne(tempFile);
            //1.2判断用户是否上传过
            if (ObjectUtils.isEmpty(file)){
                //当前用户未上传这个文件，找到别的用户上传的相同文件记录，复制一份记录给当前用户
                tempFile.setUserId(null);
                //查询数据库是否有人上传
                List<DiskFile> diskFiles = fileMapper.selectFileList(tempFile);
                if (ObjectUtils.isEmpty(diskFiles)){
                    //说明还没有人上传这个文件或者minio中已经存在但是没有记录
                    Long type = determineFileType(fileUploadChunkDto.getFileName());
                    //插入数据库文件记录，但是状态为未上传
                    DiskFile newFile = DiskFile.builder()
                            .fileName(fileUploadChunkDto.getFileName())
                            .fileMd5(fileUploadChunkDto.getFileMD5())
                            .createTime(DateUtils.getNowDate())
                            .fileSize(fileUploadChunkDto.getChunk().getSize())
                            .totalChunks(fileUploadChunkDto.getTotalChunks())
                            .userId(userId)
                            .status(0)
                            .fileType(type)
                            .build();
                    int i = fileMapper.insertFile(newFile);
                    if (i < 0) {
                        return 0;
                    }
                    return 10;
                }
                DiskFile diskFile = diskFiles.get(0);
                //随便取第一个记录复制
                diskFile.setUserId(userId);
                diskFile.setId(null);
                diskFile.setCreateTime(DateUtils.getNowDate());
                if (fileMapper.insertFile(diskFile)<0) {
                    return 0;
                }
                return 1;
            }
            //1.3用户上传过 文件上传完成
            if (ObjectUtils.isNotEmpty(file) && file.getStatus() == 1) {
                //秒传功能
                return 1;
            } else if (ObjectUtils.isNotEmpty(file) && file.getStatus() == 2) {
                //文件放入回收站
                return 2;
            }
            // TODO 文件状态未完成

        } else {
            //2.文件第一次上传该系统,Redis没有md5标识,判断文件类型
            Long type = determineFileType(fileUploadChunkDto.getFileName());
            //插入数据库文件记录，但是状态为未上传
            DiskFile newFile = DiskFile.builder()
                    .fileName(fileUploadChunkDto.getFileName())
                    .fileMd5(fileUploadChunkDto.getFileMD5())
                    .createTime(DateUtils.getNowDate())
                    .fileSize(fileUploadChunkDto.getChunk().getSize())
                    .totalChunks(fileUploadChunkDto.getTotalChunks())
                    .userId(userId)
                    .status(0)
                    .fileType(type)
                    .build();
            DiskFile file = fileMapper.selectByFileOne(newFile);
            if (ObjectUtils.isNotEmpty(file)) {
                return 3;
            }
            int i = fileMapper.insertFile(newFile);
            if (i < 0) {
                return 0;
            }
        }
        return 10;
    }
}
